using System;
using System.Collections.Generic;
using System.Configuration;
using System.Transactions;
using Nvigorate.Common;

namespace Nvigorate.Logging
{
    public class LogManager
    {
        private static List<ILogAdapter> _adapters = new List<ILogAdapter>();
        private static OnErrorLogged _handler = null;
        private static bool _initialized = false;
        private static string _applicationName = "";
        private static LogDetail _lastException;
        
        public static void LogError(Exception ex)
        {
            InvokeHandlers(ex.Source ?? "Null",
                           ex.TargetSite != null ? ex.TargetSite.Module.Assembly.FullName : "Null",
                           ex.TargetSite != null ? ex.TargetSite.DeclaringType.FullName : "Null",
                           ex.TargetSite != null ? ex.TargetSite.Name : "Null", 
                           Severities.Low,
                           ex.ToString(),
                           ex.InnerException != null ? ex.InnerException.Message : "Null");
        }

        public static void LogError(Exception ex, Severities severity)
        {
            InvokeHandlers(ex.Source ?? "Null",
                           ex.TargetSite != null ? ex.TargetSite.Module.Assembly.FullName : "Null",
                           ex.TargetSite != null ? ex.TargetSite.DeclaringType.FullName : "Null",
                           ex.TargetSite != null ? ex.TargetSite.Name : "Null", 
                           severity,
                           ex.ToString(),
                           ex.InnerException != null ? ex.InnerException.Message : "Null");
        }

        public static void LogError(Exception ex, Severities severity, string errorMessage, params string[] messageParameters)
        {
            InvokeHandlers(ex.Source ?? "Null",
                           ex.TargetSite != null ? ex.TargetSite.Module.Assembly.FullName : "Null",
                           ex.TargetSite != null ? ex.TargetSite.DeclaringType.FullName : "Null",
                           ex.TargetSite != null ? ex.TargetSite.Name : "Null", 
                           severity,
                           ex.ToString() ?? "Null",
                           errorMessage ?? "Null",
                           messageParameters);
        }

        public static void LogError(string assembly, string className, string method, Severities severity, string exceptionText)
        {
            InvokeHandlers(_applicationName, 
                           assembly, 
                           className, 
                           method, 
                           severity, 
                           exceptionText, 
                           "");
        }

        public static void LogError(string assembly, string className, string method, Severities severity, string exceptionText,
                                    string errorMessage, params string[] messageParameters)
        {
            InvokeHandlers(_applicationName, 
                           assembly, 
                           className, 
                           method, 
                           severity, 
                           exceptionText, 
                           errorMessage, 
                           messageParameters);
        }
        
        public static void RegisterLogAdapter(ILogAdapter adapter)
        {
            _adapters.Add(adapter);
            _handler += adapter.LogError;
        }

        public static void RegisterErrorHandler(OnErrorLogged errorHandler)
        {
            _handler += errorHandler;
        }

        private static void Initialize()
        {
            LogManagerSection section = (LogManagerSection) ConfigurationManager.GetSection("LogManager");

            if(section == null)
            {
                return;
            }

            _applicationName = section.Application;
            foreach (LogAdapterElement adapterElement in section.LogAdapters)
            {
                ILogAdapter adapter = (ILogAdapter) Activator.CreateInstance(adapterElement.AdapterType);
                adapter.LoadConfiguration(adapterElement.ConfigurationXml);
                RegisterLogAdapter(adapter);
            }

            _initialized = true;
        }

        private static void InvokeHandlers(string application, string assembly, string className, string method, Severities severity, string exceptionText,
                                           string errorMessage, params string[] messageParameters)
        {
            using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Suppress))
            {
                if (!_initialized)
                {
                    Initialize();
                }
                var detail = new LogDetail()
                                 {
                                     Application = application,
                                     Assembly = assembly,
                                     ClassName = className,
                                     Exception = exceptionText,
                                     MachineName = Environment.MachineName,
                                     Message = string.Format(errorMessage, messageParameters),
                                     Method = method,
                                     Severity = severity.ToString(),
                                     UserName = Environment.UserName
                                 };

                if (_handler != null && (_lastException == null || !_lastException.Equals(detail)))
                {
                    _lastException = detail;
                    _handler.Invoke(
                        detail.MachineName, 
                        detail.UserName, 
                        detail.Application, 
                        detail.Assembly, 
                        detail.ClassName, 
                        detail.Method, 
                        severity, 
                        detail.Exception, 
                        errorMessage, 
                        messageParameters);
                }
            }
        }
    }

    internal class LogDetail
    {
        internal string MachineName { get; set; }
        internal string UserName { get; set; }
        internal string Application { get; set; }
        internal string Assembly { get; set; }
        internal string ClassName { get; set; }
        internal string Method { get; set; }
        internal string Severity { get; set; }
        internal string Exception { get; set; }
        internal string Message { get; set; }

        public override bool Equals(object obj)
        {
            var detail = obj as LogDetail;
            return
                detail.MachineName.Equals(MachineName) &&
                detail.UserName.Equals(UserName) &&
                detail.Application.Equals(Application) &&
                detail.Assembly.Equals(Assembly) &&
                detail.ClassName.Equals(ClassName) &&
                detail.Method.Equals(Method) &&
                detail.Severity.Equals(Severity) &&
                detail.Message.Equals(Message);
        }

        public override string ToString()
        {
            return DelimitedBuilder.Construct(
                    new[] {MachineName, UserName, Application, Assembly, ClassName, Method, Severity, Exception, Message},
                    ", ", "", ""
                );
        }

        public LogDetail() {}

        public LogDetail(string machine, string user, string application, string assembly, string className, string method, Severities severity, string exception, string message, string[] parameters)
        {
            MachineName = machine;
            UserName = user;
            Application = application;
            Assembly = assembly;
            ClassName = className;
            Method = method;
            Severity = severity.ToString();
            Exception = exception;
            Message = string.Format(message, parameters);
        }
    }

}